grammar WJ;

options {  
  output=AST; 
} 

tokens {
	BLOCK;
	STATEMENTS;
	RETURN;
	DECLARATION;
	ASSIGNMENT;
	ARG_LIST;
	PARAM_LIST;
	FUNCTION_CALL;
	IF;
	ELSE_IF;
	ELSE;
	NEGATE;
	NOT;
	OR;
	AND;
	POW;
	ADDSUB;
	MULDIVMOD;
	RELATIONAL;
}

@parser::header {
	package wj;
	import java.util.HashMap;
}

@lexer::header {
  package wj;
}


@parser::members {

  public static HashMap<String, Function> functions = new HashMap<String, Function>();
  
    private void defineFunction(String id, ArrayList<String> parameterList,  Object block) {

    // `block` is never null.
    CommonTree blockTree = (CommonTree)block;

    // The function name with the number of parameters after it the unique key
	if(parameterList == null)
		parameterList = new ArrayList<String>();
	
    String key = id +"-"+parameterList.size();
    functions.put(key, new Function(id, parameterList, blockTree));
  }
  
}
parse  
  :  block EOF -> block
  ;  
  
block  
  :  (statement | functionDeclaration)* (Return expression '.')?
		-> ^(BLOCK ^(STATEMENTS statement*) ^(RETURN expression?))
  ;  
  
statement  
	:	assignment  '.'		-> assignment
	|	functionCall '.'	-> functionCall
	|	ifStatement			-> ifStatement
	|	whileStatement		->	whileStatement
	;  

assignment
	: 	Identifier 'gets' expression -> ^(ASSIGNMENT Identifier expression)
	;
		
functionDeclaration
	:  CreateFunction Identifier (WithParameters '(' parameterList* ')')? ':' block EndFunction  { defineFunction($Identifier.text, $parameterList.parameterList, $block.tree);}
	;

argumentList:	expression (',' expression)* -> ^(ARG_LIST expression+);

parameterList returns [ArrayList<String> parameterList]
@init{
	$parameterList = new ArrayList<String>();
}
  :   a=Identifier{$parameterList.add($a.text);} (',' b=Identifier{$parameterList.add($b.text);})* -> ^(PARAM_LIST (Identifier)+)
  ;
	
functionCall
	:	Write  ('()' | '(' expression? ')') -> ^(FUNCTION_CALL Write expression?)
	|	WriteLine ('()' | '(' expression? ')') -> ^(FUNCTION_CALL WriteLine expression?)
	|	ReadString ('()' | '(' ')')-> ^(FUNCTION_CALL ReadString)
	|	ReadNumber	('()' | '(' ')')-> ^(FUNCTION_CALL ReadNumber)
	|	Connect '(' expression (',' expression)+ ')' -> ^(FUNCTION_CALL Connect expression+)
	|	stringExpr Length -> ^(FUNCTION_CALL Length stringExpr)
	|	stringExpr CharAt '[' expression ']' -> ^(FUNCTION_CALL CharAt stringExpr expression)
	|	Take expression 'characters' 'from' stringExpr -> ^(FUNCTION_CALL Take expression stringExpr)
	|	Drop expression 'characters' 'from' stringExpr -> ^(FUNCTION_CALL Drop expression stringExpr)
	|	Identifier ('()' | '('argumentList?')') -> ^(FUNCTION_CALL Identifier argumentList?)
	;
	
stringExpr
	:	String
	|	Identifier
	;
	
ifStatement
	:	 ifStatementPart elseIfStatementPart* elseStatementPart? EndIf -> ^(IF ifStatementPart elseIfStatementPart* elseStatementPart?) 
	;
	
ifStatementPart
	:	If expression 'then' ':' block -> ^(IF expression block)
	;
	
elseIfStatementPart
	:	ElseIf expression 'then' ':' block -> ^(ELSE_IF expression block)
	;
	
elseStatementPart
	:	Else ':' block -> ^(ELSE block)
	;
	
whileStatement
	:	'while'^ expression 'is' 'correct' ':' block 'end while'
	;
	
expression
	:	andExpr ('or' andExpr)* -> ^(OR andExpr+)
	;

andExpr
	:	relExpr ('and' relExpr)* -> ^(AND relExpr+)
	;

relExpr
	: 	arimExpr (relOp arimExpr)? -> ^(RELATIONAL relOp? arimExpr+)
	;
	
relOp 
	:	'=='	
	|	'!='	
	|	'>='	
	|	'<='	
	|	'>'		
	|	'<'		
	;
	
arimExpr
	:	mulExpr (addSub mulExpr)* -> ^(ADDSUB addSub* mulExpr+)
	;

addSub  
	:	'+'		
	|	'-'		
	;
	
mulExpr 
	:	powExpr (mulDivMod powExpr)* -> ^(MULDIVMOD mulDivMod* powExpr+)
	;
	
mulDivMod 
	:	'*'		
	|	'/'		
	|	'mod'	
	;

powExpr
	:	unaryExpr ('^' unaryExpr)* -> ^(POW unaryExpr+)
	;
  
unaryExpr
	:	'-'^ atom
	|	'!'^ atom
	|	atom
	;
	
atom
	:	String
	|	Number
	|	Boolean
	|	Identifier
	|	functionCall
	|	'(' expression ')'
	;
	
If		:	'if';
ElseIf	:	'else if';
Else	:	'else';
Declare	:	'declare';
CreateFunction:	'create function named';
WithParameters:	'with parameters';
ThatReturns:	'that returns';
EndIf	:	'end if';
EndFunction	:	'end function';
Connect	:	'connect';
ReadString	:	'readString';
ReadNumber	:	'readNumber';
Write	: 	'write' ;
WriteLine: 'writeLine';
CharAt	:	' \'s character at index';
Length	:	' \'s length';
Return	: 	'return';
Take	:	'take';
Drop	:	'drop';

Number
	:   Int ('.' Digit*)?
	;

Boolean
	:	'true'
	|	'false'
	;

Identifier
  :  (AlphaChar | '_') (AlphaChar| '_' | Digit)*
  ;

  
String
@after {
  setText(getText().substring(1, getText().length()-1));
}
  :  '"'  (~'"')* '"' 
  ;
  
Comment
	: '[' Space* 'comment' Space* ']' Space* ':' ~('\r' | '\n')* { skip();}
  ;

Space
  :  (' ' | '\t' | '\r' | '\n' | '\u000C') {skip();}
  ;

fragment AlphaChar
	: 'a'..'z' | 'A'..'Z'
	;
  
fragment Int
  :  '1'..'9' Digit*
  |  '0'
  ;
  
fragment Digit 
  :  '0'..'9'
  ;
